﻿using Red.Controls.Extensions;
using Red.Controls.Utils;
using Red.Controls.ViewModels;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Threading.Tasks;

namespace Red.Controls
{
    /// <summary>
    ///     Tree control that supports icons, tooltips, commands, drag-n-drop etc
    /// </summary>
    public partial class RedTree : UserControl
    {
        #region Data

        private bool _doNotSendSelectionChanged = false;
        private DataItem _activeItem;

        #endregion Data

        #region Properties

        public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
                    "ItemsSource", typeof( IEnumerable ), typeof( RedTree ) );

        public static readonly DependencyProperty ToolbarCommandsProperty = DependencyProperty.Register(
                    "RedTreeCommands", typeof( ObservableCollection< CommandWrapper > ), typeof( RedTree ) );

        private static readonly DependencyProperty SelectedItemCommandsProperty = DependencyProperty.Register(
                    "SelectedItemCommands", typeof( ObservableCollection< CommandWrapper > ), typeof( RedTree ) );

        public static readonly DependencyProperty IsToolbarVisibleProperty = DependencyProperty.Register(
                    "IsToolbarVisible", typeof( bool ), typeof( RedTree ) );

        /// <summary>
        ///     Binding to underlaying TreeView ItemsSource
        /// </summary>
        public IEnumerable< DataItem > ItemsSource
        {
            get { return (IEnumerable< DataItem >)GetValue( ItemsSourceProperty ); }
            set { SetValue( ItemsSourceProperty, value ); }
        }

        /// <summary>
        ///     Commands collection common for all tree items
        /// </summary>
        public ObservableCollection< CommandWrapper > ToolbarCommands
        {
            get { return (ObservableCollection<CommandWrapper>)GetValue( ToolbarCommandsProperty ); }
            set { SetValue( ToolbarCommandsProperty, value ); }
        }

        private ObservableCollection< CommandWrapper > SelectedItemCommands
        {
            get { return (ObservableCollection<CommandWrapper>)GetValue( SelectedItemCommandsProperty ); }
            set { SetValue( SelectedItemCommandsProperty, value ); }
        }

        /// <summary>
        ///     Sets toolbar collapsed or visible
        /// </summary>
        public bool IsToolbarVisible 
        {
            get { return (bool)GetValue( IsToolbarVisibleProperty ); }
            set { SetValue( IsToolbarVisibleProperty, value ); }
        }

        /// <summary>
        ///     Exposes underlaying TreeView property
        /// </summary>
        public DataItem SelectedItem
        {
            get { return _treeView.SelectedItem as DataItem; }
        }

        /// <summary>
        ///     Item activated by double clicking LMB
        /// </summary>
        public DataItem ActiveItem
        {
            get { return _activeItem; }
            set
            {
                if ( _activeItem != value )
                {
                    if ( _activeItem != null )
                    {
                        _activeItem.IsActive = false;
                        _activeItem = null;
                    }

                    if ( value.CanBeActivated )
                    {
                        value.IsActive = true;
                        _activeItem = value;
                        OnActiveItemChanged( _activeItem );
                    }
                }
            }
        }

        #endregion Properties

        #region Events

        public event Action<DataItem> ActiveItemChanged;

        private void OnActiveItemChanged( DataItem activeItem )
        {
            if ( ActiveItemChanged != null )
                ActiveItemChanged( activeItem );
        }

        #endregion Events

        #region C-tor

        public RedTree()
        {
            InitializeComponent();
        }
        
        #endregion C-tor

        #region Public methods

        /// <summary>
        ///     Searches for an item by going down a given path
        /// </summary>
        /// <remarks>
        ///     Because the tree evaluation is lazy. in order to be able to find an item on a particular path, 
        ///     the method has to expand the whole tree path leading to the item.
        /// </remarks>
        /// <param name="path">
        ///     The path leading to an element, build from items' PathElement values separated by System.IO.Path.DirectorySeparatorChar
        /// </param>
        public DataItem FindItemByPath( string path )
        {
            if ( String.IsNullOrEmpty( path ) )
            {
                return null;
            }
            var root = DataContext as DataItem;
            if ( root != null )
            {
                return root.FindDescendantByPath( path );
            }
            return null;
        }

        /// <summary>
        ///     Selects an item by looking it up a given path
        /// </summary>
        /// <param name="path">
        ///     The path leading to an element, build from items' PathElement values separated by System.IO.Path.DirectorySeparatorChar
        /// </param>
        public bool SelectByPath( string path )
        {
            var item = FindItemByPath( path );
            if ( item != null )
            {
                item.IsSelected = true;
                return true;
            }
            else
            { // item not found - deselect
                var selectedItem = _treeView.SelectedItem as DataItem;
                if ( selectedItem != null )
                {
                    selectedItem.IsSelected = false;
                }
                return false;
            }
        }

        /// <summary>
        ///     Returns expanded items paths. Because expanding a child always implies expanding its parent - this list
        ///     contains the minimal set needed to restore the expansion state - only children in this case.
        /// </summary>
        public List<string> GetExpandedItems()
        {
            var expandedPaths = new List<string>();
            var root = DataContext as DataItem;
            if ( root != null )
            {
                StoreExpansionState( root, expandedPaths );
            }
            return expandedPaths;
        }

        /// <summary>
        ///     Expand all items given by paths.
        /// </summary>
        public void RestoreExpandedItems( List<string> paths )
        {
            foreach ( string path in paths )
            {
                var item = FindItemByPath( path );
                if ( item != null )
                {
                    item.IsExpanded = true;
                }
            }
        }

        /// <summary>
        ///     Refreshes the tree content to reflect data changes. Preserves the expansion state and selection if corresponding items
        ///     still exist after the rebuild.
        /// </summary>
        public async Task Refresh()
        {
            var selectedItem = _treeView.SelectedItem as DataItem;
            string selectedPath = selectedItem != null ? selectedItem.Path : null;
            List<string> expandedItems = GetExpandedItems();
            _doNotSendSelectionChanged = true;

            try
            {
                var root = DataContext as DataItem;
                await root.RebuildAsync();
                RestoreExpandedItems( expandedItems );
                if ( selectedPath != null )
                {
                    SelectByPath( selectedPath );
                }
            }
            finally
            {
                _doNotSendSelectionChanged = false;
            }
        }

        #endregion Public methods

        #region Private methods

        private bool StoreExpansionState( DataItem item, List<string> paths )
        {
            bool hasExpandedChildren = false;

            foreach ( DataItem child in item.Children )
                if ( StoreExpansionState( child, paths ) )
                    hasExpandedChildren = true;

            // we try to be smart and only include items with no further expanded children - because each of such child
            // would expand its parent anyway
            if ( item.IsExpanded && !hasExpandedChildren ) 
                paths.Add( item.Path );

            return item.IsExpanded;
        }
        
        #endregion Private methods

        #region Event handlers

        private void Tree_DragStart( object sender, DragStartEventArgs e )
        {
            TreeViewItem container = Helpers.FindVisualAncestorOfType<TreeViewItem>( e.OriginalSource );
            if ( container != null )
            {
                var item = _treeView.SelectedItem as DataItem;
                if ( item != null )
                {
                    e.DraggedData = Task.FromResult( item.GetDraggedData() );
                }
                else
                {
                    e.DraggedData = Task.FromResult<IDataObject>( null );
                }
            }
        }

        private void Tree_DragEnter( object sender, DragEventArgs e )
        {
            TreeViewItem container = Helpers.FindVisualAncestorOfType<TreeViewItem>( e.OriginalSource );
            if ( container != null )
            {
                var item = container.DataContext as DataItem;
                if ( item.AcceptsDraggedData( e.Data ) )
                {
                    item.IsDropTarget = true; // enable visual hint
                }
            }
        }

        private void Tree_DragLeave( object sender, DragEventArgs e )
        {
            TreeViewItem container = Helpers.FindVisualAncestorOfType<TreeViewItem>( e.OriginalSource );
            if ( container != null )
            {
                var item = container.DataContext as DataItem;
                item.IsDropTarget = false;
            }
        }

        private void Tree_Drop( object sender, DragEventArgs e )
        {
            TreeViewItem container = Helpers.FindVisualAncestorOfType<TreeViewItem>( e.OriginalSource );
            if ( container != null )
            {
                var item = container.DataContext as DataItem;
                item.DropDraggedData( e.Data );
                item.IsDropTarget = false;
            }
        }

        private void Tree_SelectedItemChanged( object sender, RoutedPropertyChangedEventArgs<object> e )
        {
            var selectedItem = _treeView.SelectedItem as DataItem;
            var selectedItemPrimaryCommands = new ObservableCollection<CommandWrapper>();

            if ( selectedItem != null )
            {
                foreach( var command in selectedItem.Commands )
                {
                    if ( command.Command.IsPrimary )
                        selectedItemPrimaryCommands.Add( command );
                }

                selectedItem.OnSelect();
            }

            SelectedItemCommands = selectedItemPrimaryCommands;

            if ( _doNotSendSelectionChanged )
            {
                e.Handled = true; // suppress bubbling up
            }
        }

        private void Tree_PreviewMouseDoubleClick( object sender, MouseButtonEventArgs e )
        {
            var cachedActiveItem = ActiveItem;
            ActiveItem = _treeView.SelectedItem as DataItem;

            // active item has changed, prevent expanding
            if ( ActiveItem != cachedActiveItem )
                e.Handled = true;
        }

        private void Tree_MouseDoubleClick( object sender, MouseButtonEventArgs e )
        {
            var selectedItem = _treeView.SelectedItem as RedTreeItem;
            if ( null != selectedItem )
                selectedItem.OnSelectDoubleClick();            
        }        
        #endregion Event handlers
    }
}
